use std::{rc::Rc, cell::RefCell, fmt, sync::{Arc, Mutex}};

use crate::{native, rtda::{Frame, heap::Class, Thread, ExtraType, Object}};

pub fn init() {
    native::register("java/lang/Throwable", "fillInStackTrace", "(I)Ljava/lang/Throwable;", fill_in_stack_trace);
}

// private native Throwable fillInStackTrace(int dummy);
// (I)Ljava/lang/Throwable;
fn fill_in_stack_trace(frame: Rc<RefCell<Frame>>) {
    let this = frame.borrow().get_local_vars().borrow().get_this();
    frame.borrow().get_operand_stack().borrow_mut().push_ref(this);

    let stes = create_stack_trace_elements(this.unwrap(), frame.borrow().get_thread());
    unsafe { &mut *this.unwrap() }.set_extra(Some(ExtraType::StackTraceElements(stes)))
}

// 记录java虚拟机帧栈信息
#[derive(Clone)]
pub struct StackTraceElement {
    file_name: String,
    class_name: String,
    method_name: String,
    line_number: i32, // 帧正在执行哪行代码
}

impl fmt::Display for StackTraceElement {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let s = format!("{}.{}({}:{})"
                                , self.class_name, self.method_name, self.file_name, self.line_number);
        write!(f, "{}", s)
    }
}

fn create_stack_trace_elements(t_obj: *mut Object, thread: Arc<Mutex<Thread>>) -> Vec<StackTraceElement> {
    // 栈顶两帧，正在执行的fillInStackTrace(int)和即将执行的fillInStackTrace()方法，需要跳过这两帧。
    let skip = distance_to_object(unsafe { &*t_obj }.get_class()) + 2;
    let frames = &thread.lock().unwrap().get_frames()[skip..];
    let mut stes = Vec::with_capacity(frames.len());
    for frame in frames {
        stes.push(create_stack_trace_element(frame));
    }
    stes
}

// ParseIntTest无参数测试 全部栈信息。distance_to_object(class) 
// 参数class此时为IndexOutOfBoundsException共计跳过四条信息
// at java.lang.Throwable.fillInStackTrace(Throwable.java:-2)
// at java.lang.Throwable.fillInStackTrace(Throwable.java:784)
// at java.lang.Throwable.<init>(Throwable.java:266)
// at java.lang.Exception.<init>(Exception.java:66)
// at java.lang.RuntimeException.<init>(RuntimeException.java:62)
// at java.lang.IndexOutOfBoundsException.<init>(IndexOutOfBoundsException.java:56)
// at java.ParseIntTest.bar(ParseIntTest.java:19)
// at java.ParseIntTest.foo(ParseIntTest.java:11)
// at java.ParseIntTest.main(ParseIntTest.java:6)
fn distance_to_object(class: Arc<Class>) -> usize {
    let mut distance = 0usize;
    let mut c = class;
    while let Some(super_class) = c.get_super_class() {
        distance += 1;
        c = super_class;
    }
    distance
}

fn create_stack_trace_element(frame: &Rc<RefCell<Frame>>) -> StackTraceElement {
    let method = frame.borrow().get_method();
    let method = method;
    let class = method.get_class();
    StackTraceElement { 
        file_name: class.get_source_file(), 
        class_name: class.java_name(), 
        method_name: method.get_name(), 
        line_number: method.get_line_number(frame.borrow().get_next_pc() as i32 - 1), 
    }
}
